//-------------------------------------------------------------------
//	Copyright (c) 2012-2014 Zhirnov Andrey
//	This file is part of the "UXGEN-ES" project.
//	See copyright notice in "Engine.h".
//-------------------------------------------------------------------

#pragma once

#include "Graphics/Systems/BatchMaterial.h"

namespace Engine
{
namespace Graphics
{
	
	class BatchRender;
	SHARED_POINTER( BatchRender );



	//
	// Batch Render
	//

	class BatchRender : public BaseObject
	{
	// types
	public:
		typedef binary_array_t		vertex_array_t;
		typedef uint16_array_t		index_array_t;

		//
		// Batch Data
		//
		struct BatchData
		{
			BatchMaterial		material;
			gl_primitive::type	primitive;
			vertex_array_t		vertices;
			index_array_t		indices;
		};

	private:

		//
		// Batch
		//
		struct Batch
		{
			BatchMaterial		material;
			gl_primitive::type	primitive;
			uint				vertOffset;
			uint				vertexCount;
			uint				indexOffset;
			uint				indexCount;
			TypeId_t			attribIndex;
			///
			Batch () :
				primitive(gl_primitive::TRIANGLE), vertOffset(0), indexOffset(0), indexCount(0),
				vertexCount(0) {}
		};

		typedef array< Batch >						batch_array_t;
		typedef map< TypeId_t, GLVertexAttribs >	vertex_attribs_t;
		typedef static_stack< frect, 32 >			scissor_stack_t;


	// variables
	private:
		batch_array_t		_batches;
		vertex_attribs_t	_attribs;
		vertex_array_t		_vertices;
		index_array_t		_indices;
		scissor_stack_t		_scissor;
		bool				_changed;
		bool				_scissorTest;


	// methods
	public:
		BatchRender (const EngineSubSystemsRef ess);
		~BatchRender ();

		template <typename PrimitiveType>
		void SetMaterial (const BatchMaterial material);

		template <typename T>
		void AddBatch (const T &primitive);

		template <typename T>
		void AddBatch (const BatchMaterial &material, const T &primitive);
		
		template <typename T>
		void NewBatch (const BatchMaterial &material, const T &primitive);

		uint GetLastBatchIndex () const			{ return _batches.LastIndex(); }

		bool GetBatch (uint index, OUT BatchData &data) const;

		void Flush ();
		void Clear ();


		// Low Level //
		uint GetCurrentIndex () const;
		
		template <typename Vertex>
		void AddVertex (const Vertex &vert);
		void AddIndex (uint16 i);
		void AddIndexWithOffset (uint16 i = 0);
		void AddIndicesWithOffset (const uni_array<const uint16> indices);


		// Scissor //
		void ScissorTest (bool enabled);
		bool IsScissorTest () const				{ return _scissorTest; }

		void Scissor (const rect &region);
		void AddScissor (const rect &region);

		void PushScissor ();
		void PopScissor ();


		// Statistics //
		usize	GetVerticesCount ()	const		{ return _vertices.Count(); }
		usize	GetIndicesCount ()	const		{ return _indices.Count(); }
		usize	GetBatchesCount ()	const		{ return _batches.Count(); }
		usize	GetMemSize ()		const		{ return _vertices.Size() + _indices.Size() + _batches.Size(); }


		static BatchRenderPtr	New (const EngineSubSystemsRef ess);


	private:
		template <typename T>
		void _AddBatch (const BatchMaterial &material, const T &primitive);

		template <typename T>
		void _AddPrimitive (const T &primitive);

		template <typename T>
		void _AddPrimitiveWithoutScissor (const T &primitive);

		template <typename T>
		void _CompareBatches (const BatchMaterial &material, const T &primitive);

		template <typename Vertex>
		TypeId_t _GetAttribIndex ();

		static bool _ComparePrimitives (gl_primitive::type left, gl_primitive::type right);
	};


	
	
/*
=================================================
	SetMaterial
=================================================
*/
	template <typename PrimitiveType>
	inline void BatchRender::SetMaterial (const BatchMaterial material)
	{
		_batches.Resize( _batches.Count() + 1 );

		ASSERT( material._program.IsNotNull() );

		Batch &	batch = _batches.Back();

		batch.material		= material;
		batch.attribIndex	= _GetAttribIndex< typename PrimitiveType::vertex_t >();
		batch.primitive		=  PrimitiveType::Primitive();
		batch.vertOffset	= _vertices.Size();
		batch.indexOffset	= _indices.Size();
		batch.indexCount	= 0;
		batch.vertexCount	= 0;
	}
	
/*
=================================================
	AddBatch
=================================================
*/
	template <typename T>
	inline void BatchRender::AddBatch (const T &primitive)
	{
		CHECK_ERR( not _batches.Empty(), void() );

		if ( _ComparePrimitives( _batches.Back().primitive, T::Primitive() ) )
			return _AddPrimitive( primitive );
		else
			return _AddBatch( _batches.Back().material, primitive );
	}
	
/*
=================================================
	AddBatch
=================================================
*/
	template <typename T>
	inline void BatchRender::AddBatch (const BatchMaterial &material, const T &primitive)
	{
		if ( not _batches.Empty() )
		{
			const Batch &	batch = _batches.Back();

			if ( batch.material.Compare( material ) and
				 _ComparePrimitives( batch.primitive, T::Primitive() ) )
			{
				return _AddPrimitive( primitive );
			}
		}

		return _AddBatch( material, primitive );
	}
	
/*
=================================================
	NewBatch
=================================================
*/
	template <typename T>
	inline void BatchRender::NewBatch (const BatchMaterial &material, const T &primitive)
	{
		return _AddBatch( material, primitive );
	}

/*
=================================================
	_AddBatch
=================================================
*/
	template <typename T>
	inline void BatchRender::_AddBatch (const BatchMaterial &material, const T &primitive)
	{
		SetMaterial<T>( material );
		return _AddPrimitive( primitive );
	}

/*
=================================================
	_AddPrimitive
=================================================
*/
	template <typename T>
	inline void BatchRender::_AddPrimitive (const T &primitive)
	{
		if ( not _scissorTest )
			return _AddPrimitiveWithoutScissor( primitive );

		switch ( primitive.ScissorTest( _scissor.Get() ) )
		{
			case -1 : break;
			case  0 : _AddPrimitiveWithoutScissor( primitive );  break;
			case  1 : _AddPrimitiveWithoutScissor( primitive.Crop( _scissor.Get() ) ); break;

			DEBUG_ONLY(
				default : WARNING( "Unknown result of scissor test!" );
			)
		}
	}
	
/*
=================================================
	_AddPrimitiveWithoutScissor
=================================================
*/
	template <typename T>
	inline void BatchRender::_AddPrimitiveWithoutScissor (const T &primitive)
	{
		CHECK_ERR( T::Primitive() == _batches.Back().primitive, void() );

		Batch & batch = _batches.Back();
		usize	count = _vertices.Count();

		if ( batch.vertexCount + primitive.VerticesCount() >= MaxValue<uint16>() )
		{
			SetMaterial<T>( batch.material );
			return _AddPrimitiveWithoutScissor( primitive );
		}

		_vertices.Resize( count + primitive.VerticesCount() * sizeof(typename T::vertex_t) );
		primitive.GetVertices( _vertices.ptr() + count );

		count = _indices.Count();

		_indices.Resize( count + primitive.IndexCount() );
		primitive.GetIndices( _indices.ptr() + count, batch.vertexCount );

		batch.indexCount += primitive.IndexCount();
		batch.vertexCount += primitive.VerticesCount();

		_changed = true;
	}
	
/*
=================================================
	_CompareBatches
=================================================
*/
	template <typename T>
	inline void BatchRender::_CompareBatches (const BatchMaterial &material, const T &primitive)
	{
			
	}
	
/*
=================================================
	_GetAttribIndex
=================================================
*/
	template <typename Vertex>
	inline TypeId_t BatchRender::_GetAttribIndex ()
	{
		usize	index		= -1;
		TypeId_t  type_id	= Vertex::GetTypeId();

		if ( _attribs.FindIndex( type_id, index ) )
		{
			return type_id; //index;
		}

		index = _attribs.Add( type_id, Vertex::GetAttribs() );
		return type_id;
	}
	
/*
=================================================
	Scissor
=================================================
*/
	inline void BatchRender::Scissor (const rect &region)
	{
		_scissor.Set( region );
	}
	
/*
=================================================
	AddScissor
=================================================
*/
	inline void BatchRender::AddScissor (const rect &region)
	{
		rect	new_region;

		_scissor.Get().CropRect( new_region );
		_scissor.Set( new_region );
	}
	
/*
=================================================
	PushScissor
=================================================
*/
	inline void BatchRender::PushScissor ()
	{
		_scissor.Push();
	}

/*
=================================================
	PopScissor
=================================================
*/
	inline void BatchRender::PopScissor ()
	{
		_scissor.Pop();
	}
	
/*
=================================================
	GetCurrentIndex
=================================================
*/
	inline uint BatchRender::GetCurrentIndex () const
	{
		CHECK_ERR( not _batches.Empty(), 0 );

		Batch const &	batch = _batches.Back();

		if ( batch.indexCount == 0 )
			return 0;
		
		return _indices[ batch.indexOffset + batch.indexCount - 1 ];
	}
	
/*
=================================================
	AddIndex
=================================================
*/
	inline void BatchRender::AddIndex (uint16 i)
	{
		CHECK_ERR( not _batches.Empty(), void() );

		_batches.Back().indexCount++;
		_indices.PushBack( i );
	}
	
/*
=================================================
	AddIndexWithOffset
=================================================
*/
	inline void BatchRender::AddIndexWithOffset (uint16 i)
	{
		CHECK_ERR( not _batches.Empty(), void() );
		
		_indices.PushBack( i + _batches.Back().vertexCount );
		_batches.Back().indexCount++;
	}

/*
=================================================
	AddIndicesWithOffset
=================================================
*/
	inline void BatchRender::AddIndicesWithOffset (const uni_array<const uint16> indices)
	{
		CHECK_ERR( not _batches.Empty(), void() );
		
		const uint	offset = _batches.Back().vertexCount;

		_batches.Back().indexCount += indices.Count();

		FOR( i, indices ) {
			_indices.PushBack( indices[i] + offset );
		}
	}

/*
=================================================
	AddVertex
=================================================
*/
	template <typename Vertex>
	inline void BatchRender::AddVertex (const Vertex &vert)
	{
		CHECK_ERR( not _batches.Empty(), void() );
		CHECK_ERR( _GetAttribIndex< Vertex >() == _batches.Back().attribIndex, void() );

		_batches.Back().vertexCount++;
		_vertices.Append( uni_binbuffer_t::FromType( vert ) );
	}
	
/*
=================================================
	New
=================================================
*/
	inline BatchRenderPtr BatchRender::New (const EngineSubSystemsRef ess)
	{
		return BaseObject::_New( new BatchRender( ess ) );
	}
	
/*
=================================================
	_ComparePrimitives
=================================================
*/
	inline bool BatchRender::_ComparePrimitives (gl_primitive::type left, gl_primitive::type right)
	{
		/*if ( left != right )
			return false;

		switch ( left )
		{
			case gl_primitive::LINE_LOOP :
			case gl_primitive::LINE_STRIP :
			case gl_primitive::TRIANGLE_FAN :
			case gl_primitive::TRIANGLE_STRIP :
				return false;
		}
		return true;*/

		return left == right;
	}


}	// Graphics
}	// Engine
